package br.unicamp.mc715.order;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import br.unicamp.mc715.common.MC715Exception;
import br.unicamp.mc715.common.MC715IOException;
import br.unicamp.mc715.common.MC715SerializationException;
import br.unicamp.mc715.transport.Message;
import br.unicamp.mc715.transport.Transport;
import br.unicamp.mc715.transport.UDPTransport;

public class Communicator {

	public static final int TAM_MSG = 500;
	public static final double MAX_MILLIS = 600000;
	private Transport transport;
	private int pmId = 0;
	private double startTimeAccur;
	private String type;
	private int instancia;

	Writer file_l, file_o;

	Communicator(String type, int inst) throws MC715Exception, IOException
	{
		transport = new UDPTransport();
		Log.logger.info(transport.getId().toString());
		startTimeAccur = ((double)System.nanoTime())/1000000.0;
		this.type = type;
		this.instancia = inst;
	}

	public void sendPing(PingMessage pm) 
	{	
		try {
			transport.sendMessage(pm);
		} catch (MC715IOException e) {
			e.printStackTrace();
		} catch (MC715SerializationException e) {
			e.printStackTrace();
		}
	}

	public void receivePing() throws IOException 
	{	
		PingMessage pm = null;
		try {
			Message msg = transport.receiveMessage(500);

			if (msg == null)
			{
				return;
			}

			pm = (PingMessage) msg.getPayload();

			/* Caso seja uma mensagem recebida e originada de processo diferente do atual */
			if ( ! pm.senderId.equals(transport.getId()))
			{
				Log.logger.info(pm.toString() + pm.number);
				file_o.write((getElapsed()/1000.0) + " " + pm.number + "\n");
				transport.sendMessage(pm, msg.getSender());
			}
			/* Caso a mensagem recebida tenha se orginado no processo atual */
			else
			{
				/* Mensagem eh "eco" de uma anteriormente enviada por este processo */
				if (! transport.getId().equals(msg.getSender())) 
				{
					Log.logger.info((getElapsed()/1000.0) + " " + pm.getLatency());
					file_l.write((getElapsed()/1000.0) + " " + pm.getLatency() + "\n");
				}
			}
		} catch (MC715IOException e) {
			e.printStackTrace();
		} catch (MC715SerializationException e) {
			e.printStackTrace();
		}
	}
	
	private double getAccurTime() {
		return ((double)System.nanoTime())/1000000.0;
	}

	/* Thread to receive messages */
	private class ReceiverThread extends Thread {

		public ReceiverThread() {
			setDaemon(false);
		}

		public void run() {
			if (type.equals("sender")) {
				try {
					file_l = new BufferedWriter(new FileWriter("../logs/latency.log", false));
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			} else if (type.equals("receiver")) {
				try {
					file_o = new BufferedWriter(new FileWriter("../logs/order" + instancia + ".log", false));
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			while (getElapsed() < MAX_MILLIS)
			{
				try {
					receivePing();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (type.equals("sender")) {
				try {
					file_l.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else if (type.equals("receiver")) {
				try {
					file_o.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/* Thread to send messages */
	private class SenderThread extends Thread {

		public SenderThread() {
			setDaemon(false);
		}

		public void run() {
			while (getElapsed() < MAX_MILLIS) {
				sendPing(new PingMessage(TAM_MSG, pmId++, transport.getId()));
			}
		}
	}

	private void startSender() {
		Thread sender;
		sender = new SenderThread();		
		sender.start();
	}

	private void startReceiver() {
		Thread receiver;
		receiver = new ReceiverThread();
		receiver.start();		
	}

	public double getElapsed() {
		return (getAccurTime() - startTimeAccur);
	}

	public static void main(String args[]) throws IOException, MC715Exception
	{
		Communicator communicator;

		new File("../logs/").mkdirs();
		communicator = new Communicator(args[0], Integer.parseInt(args[1]));
		
		if(args[0].equals("sender"))
		{
			Log.logger.info("Sender");
			communicator.startSender();
			communicator.startReceiver();
		}
		else if(args[0].equals("receiver"))
		{
			Log.logger.info("Receiver");
			communicator.startReceiver();
		}
		else
		{
			System.out.println("sender or receiver");
		}
	}
	
}
